package beautiful.butterfly.server.httpserver.handlers;

import beautiful.butterfly.server.application.Application;
import beautiful.butterfly.server.application.Environment;
import beautiful.butterfly.server.httpserver.kit.NamedThreadFactory;
import beautiful.butterfly.server.httpserver.mvc.Constant;
import beautiful.butterfly.server.httpserver.mvc.SessionContent;
import beautiful.butterfly.server.httpserver.mvc.http.Session;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.util.ResourceLeakDetector;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.*;

import static beautiful.butterfly.server.httpserver.mvc.Constant.*;


@Slf4j
public class HttpServer implements IHttpServer {

    public static final String ____ = "config.properties";


    private String path;
    private String staticFilePath;
    private String templatesPath;
    private String fileUploadPath;
    //
    private String server_address;
    private int server_port;
    private String webRootPath;
    //
    private Application application;
    private Environment environment = new Environment();
    private EventLoopGroup bossEventLoopGroup;
    private EventLoopGroup workerEventLoopGroup;
    private ExecutorService bossExecutorService;
    private ExecutorService workerExecutorService;
    private int threadCount;
    private int workers;
    private int backlog;
    private Channel channel;
    //
    private SessionContent sessionContent = new SessionContent();

    public String getWebRootPath() {
        return webRootPath;
    }

    public SessionContent getSessionContent() {
        return sessionContent;
    }

    @Override
    public void start(Application application) throws Exception {
        this.application = application;
        this.environment = application.environment();
        //
        this.path = application.getPath();
        //读取核心配置文件路径
        String coreConfigPath = this.path + File.separator + ____;
        Properties properties = new Properties();
        FileInputStream fileInputStream = new FileInputStream(new File(coreConfigPath));
        properties.load(fileInputStream);
        fileInputStream.close();
        this.environment.getProperties().putAll(Environment.of(properties).getProperties());
        //
        try {
            this.initConfig();
            this.shutdownHook();
            this.startServer();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    //
    public String getPath() {
        return path;
    }

    public String getStaticFilePath() {
        return staticFilePath;
    }

    public String getTemplatesPath() {
        return templatesPath;
    }

    public String getFileUploadPath() {
        return fileUploadPath;
    }
    //


    private void startServer() throws Exception {

        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);

        boolean sslEnable = environment.getBoolean(ssl_enable, false);

        if (sslEnable) {
            this.webRootPath = "https://" + this.server_address + ":" + this.server_port;
        } else {
            this.webRootPath = "http://" + this.server_address + ":" + this.server_port;
        }
        if (Application.developMode) {
            log.info("webRootPath:" + this.webRootPath);
        }
        SslContext sslContext = null;
        if (sslEnable) {

            String certFilePath = environment.getString(ssl_cert_path, null);
            String privateKeyPath = environment.getString(ssl_private_key, null);
            String privateKeyPassword = environment.getString(ssl_private_key_password, null);

            log.info("配置ssl:" + "certFilePath:" + certFilePath + "privateKeyPath:" + "privateKeyPassword:" + privateKeyPassword);
            sslContext = SslContextBuilder.forServer(new File(certFilePath), new File(privateKeyPath), privateKeyPassword).build();
        }

        // Configure the handlers.
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.option(ChannelOption.SO_BACKLOG, backlog);
        serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
        serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);


        if (EpollEventLoopGroups.epollIsAvailable()) {

            serverBootstrap.option(EpollChannelOption.SO_REUSEPORT, true);

            Group group = EpollEventLoopGroups.group(threadCount, bossExecutorService, workers, workerExecutorService);
            this.bossEventLoopGroup = group.getBoosMultithreadEventLoopGroup();
            this.workerEventLoopGroup = group.getWorkerMultithreadEventLoopGroup();
            serverBootstrap.group(bossEventLoopGroup, workerEventLoopGroup).channel(group.getSocketChannel());
        } else {

            this.bossEventLoopGroup = new NioEventLoopGroup(threadCount, bossExecutorService);
            this.workerEventLoopGroup = new NioEventLoopGroup(workers, workerExecutorService);
            serverBootstrap.group(bossEventLoopGroup, workerEventLoopGroup).channel(NioServerSocketChannel.class);
        }

        serverBootstrap.handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(new HttpServiceChannelInitializer(sslContext, application, this));
        int port = environment.getInt(Constant.server_port, default_server_port);
        channel = serverBootstrap.bind(port).channel();
        //
        if (Application.developMode) {
            log.error("启动:session清理程序");
        }
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new Callable() {
            @Override
            public Object call() {
                if (Application.developMode) {
                    log.error("清理session");
                }
                clearSession();
                //nothing to return
                return null;
            }
        }, Long.parseLong(environment.getString("session_time_out", "60")), TimeUnit.SECONDS);//测试环境

    }

    private void clearSession() {
        Map<String, Session> sessionIdToSessionMap = this.sessionContent.getSessionIdToSessionMap();
        if (sessionIdToSessionMap != null) {
            Set<String> sessionIdSet = sessionIdToSessionMap.keySet();
            for (String sessionId :
                    sessionIdSet) {
                Session session = sessionIdToSessionMap.get(sessionId);
                if (session != null) {
                    if (System.currentTimeMillis() / 1000 - session.getExpireTime() > Long.parseLong(environment.getString("session_time_out", "60"))) {
                        this.sessionContent.remove(session);
                    }

                }
            }
        }
    }


    private void initConfig() {
        //
        this.server_address = environment.getString(Constant.server_address, default_server_address);
        this.server_port = environment.getInt(Constant.server_port, default_server_port);
        //
        this.staticFilePath = Environment.pathFix(environment.getString(Constant.static_file_path, this.path + default_static_file_path));
        this.templatesPath = Environment.pathFix(environment.getString(Constant.templates_path, environment.pathFix(this.path + default_templates_path)));
        this.fileUploadPath = Environment.pathFix(environment.getString(Constant.file_upload_path, environment.pathFix(this.path + default_file_upload_path)));

        log.info("静态文件路径:" + staticFilePath);
        log.info("模版文件路径:" + templatesPath);
        log.info("文件上传地址:" + fileUploadPath);


        String boosGroupName = environment.getString(ENV_KEY_NETTY_BOOS_GROUP_NAME, "netty-boss");
        String workerGroupName = environment.getString(ENV_KEY_NETTY_WORKER_GROUP_NAME, "netty-worker");

        bossExecutorService = Executors.newCachedThreadPool(new NamedThreadFactory("boss@" + boosGroupName));
        workerExecutorService = Executors.newCachedThreadPool(new NamedThreadFactory("worker@" + workerGroupName));

        threadCount = environment.getInt(ENV_KEY_NETTY_THREAD_COUNT, 1);
        workers = environment.getInt(ENV_KEY_NETTY_WORKERS, 0);
        backlog = environment.getInt(ENV_KEY_NETTY_SO_BACKLOG, 8192);
    }

    private void shutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                HttpServer.this.stop();
            }
        });
    }

    @Override
    public void stop() {

        try {
            if (this.bossEventLoopGroup != null) {
                this.bossEventLoopGroup.shutdownGracefully();
            }
            if (this.workerEventLoopGroup != null) {
                this.workerEventLoopGroup.shutdownGracefully();
            }
            if (bossExecutorService != null) {
                bossExecutorService.shutdown();
            }
            if (workerExecutorService != null) {
                workerExecutorService.shutdown();
            }

        } catch (Exception e) {

        }
    }


}
